/**
  对象拷贝
      1. JSON.parse(JSON.stringify(obj))
          缺点: 数据类型是Function或数据值为undefined无法拷贝
            const obj = {
                name: 'jack',           
                age: 18,
                hobby: {swiming:'游泳'},
                arr: [{ score: 98 }],
                say: () => {},         // Function    无法拷贝
                number: undefined,     // undefined   无法拷贝
            }
      2. Object.assign(obj)或展开运算符{...obj}
          缺点：只能拷贝一层,如果属性值是对象，无法拷贝
            const obj = {
                name: 'jack',           
                age: 18,
                hobby: {swiming:'游泳'}, // Object   无法拷贝
                arr: [{ score: 98 }],   // Array     无法拷贝
                say: () => {},         // Function   无法拷贝
                number: undefined,     // undefined  
            } 
      3. 递归 cloneDeep()

      4. 优化:
            1. 如果对象属性值没有对象，只有一层,使用展开运算符{...obj}
            2. 如果对象数据类型不是Function也不是数据值为undefined,使用JSON.parse(JSON.stringify(obj))
            3. 否则使用递归
              
 */

/**
 * 使用 JSON.parse(JSON.stringify(obj)) 拷贝
 */
const testJSON = () => {
	const obj = {
		name: 'jack',
		age: 18,
		hobby: { swiming: '游泳' }, // Object
		arr: [{ score: 98 }], // Array
		say: () => {}, //Function
		number: undefined, //undefined
	}

	const cloneObj = JSON.parse(JSON.stringify(obj))
	console.log('obj :', obj, '\n cloneObj :', cloneObj)
}

/**
 * 使用 1. Object.assign(obj)
 *      2. 展开运算符{...obj}
 */
const testAssign = () => {
	const obj = {
		name: 'jack',
		age: 18,
		hobby: { swiming: '游泳' }, // Object
		arr: [{ score: 98 }], // Array
		say: () => {console.log('say>>')}, //Function
		number: undefined, //undefined
	}

	// const cloneObj = Object.assign(obj)
	const cloneObj = { ...obj }
	cloneObj.name = 'rose'
	cloneObj.age = 20
	cloneObj.hobby.swiming = '不会游泳' // 无法拷贝,cloneObj.hobby == obj.hobby
    cloneObj.arr[0].score = 100
	cloneObj.say()
	console.log('obj :', obj, '\n cloneObj :', cloneObj)
}

/**
 * 深拷貝-递归实现
 * @param {*} data
 * @returns
 */
const cloneDeep = data => {
	const newData = Array.isArray(data) ? [] : {}
	for (let key in data) {
		if (data[key] && typeof data[key] === 'object') {
			newData[key] = cloneDeep(data[key])
		} else {
			newData[key] = data[key]
		}
	}
	return newData
}
/**
 * test递归-拷贝
 */
const testCloneDeep = () => {
	const obj = {
		name: 'jack',
		age: 18,
		hobby: { swiming: '游泳' }, // Object
		arr: [{ score: 98 }],
		say: () => {}, //Function
		number: undefined, //undefined
		students: [
			{
				student: {
					num: {
						id: 1,
					},
				},
			},
		],
	}
	const cloneObj = cloneDeep(obj)
	cloneObj.name = 'rose'
	cloneObj.age = 20
	cloneObj.hobby.swiming = '不会游泳'
	cloneObj.arr[0].score = 100
	cloneObj.students[0].student.num.id = 100
	console.log('obj :', obj, '\n cloneObj :', cloneObj)
	console.log(cloneObj.students[0].student.num.id === obj.students[0].student.num.id)
}

/**
 * 判断对象属性值是否有对象
 * @param {} data
 * @returns
 */
const isObjectValue = data => {
	for (let key in data) {
		if (data[key] && typeof data[key] === 'object') {
			if(Object.prototype.toString.call(data[key]) !== '[object Function]'){
                return true
            }
		}
	}
}

/**
 * 递归判断数据类型
 *   Function或undefined返回为true
 */
const isFunctionOrUndefined = data => {
	for (let key in data) {
		if (data[key] === undefined) {
			return true
		} else if (data[key] && Object.prototype.toString.call(data[key]) === '[object Function]') {
			return true //Function
		} else if (data[key] && typeof data[key] === 'object') {
			isFunctionOrUndefined(data[key])
		}
	}
}

/**
 * 拷贝对象 优化方案
 * @param {*} obj 原对象
 * @param {*} cloneOjb 返回拷贝对象
 */
const cloneDeepObj = obj => {
	if (obj === undefined) {
		throw new TypeError('param is not undefined')
	}
	//判断拷贝对象只有一层及属性值都不是对象,使用Object.assign()
	if (!isObjectValue(obj)) {
		return  { ...obj }
	}
	//判断类型,如果不是Function或undefined使用JSON方式
	if (!isFunctionOrUndefined(obj)) {
		return JSON.parse(JSON.stringify(obj))
	}

    return cloneDeep(obj)

}

const testCloneDeepObj = () => {
	const obj = {
		name: 'jack',
		age: 18,
		hobby: { swiming: '游泳' },
		arr: [{ score: 98 }],
		say: function(){console.log(this.name)},
		number: undefined,
	}
	const cloneObj = cloneDeepObj(obj)
	cloneObj.name = 'rose'
	cloneObj.age = 20
	cloneObj.hobby.swiming = '不会游泳'
	cloneObj.arr[0].score = 100
	console.log('obj :', obj, '\n cloneObj :', cloneObj)
    cloneObj.say()
    obj.say()
}

testCloneDeepObj()
